/*
 * HTDraw.java
 * www.bouthier.net
 *
 * The MIT License :
 * -----------------
 * Copyright (c) 2001-2003 Christophe Bouthier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

package hypertree.model;

import java.awt.Graphics2D;
import java.awt.Insets;

import javax.swing.SwingUtilities;

/**
 * The HTDraw class implements the drawing model for the HTView.
 * 
 * @author Christophe Bouthier [bouthier@loria.fr]
 * @version 1.0
 */
public class HTDraw
{

	private final static int	NBR_FRAMES		= 5;		// number of
	// intermediates
	// animation frames

	private HTModel				model			= null;	// the tree model
	private HTView				view			= null;	// the view using this
	// drawing model
	private HTDrawNode			drawRoot		= null;	// the root of the
	// drawing tree

	private HTCoordS			sOrigin			= null;	// origin of the screen
	// plane
	private HTCoordS			sMax			= null;	// max point in the
	// screen plane

	private double[]			ray				= null;

	private boolean				fastMode		= false;	// fast mode
	private boolean				longNameMode	= false;	// long name mode
	private boolean				kleinMode		= false;	// klein mode

	public static boolean		isTranslating	= false;

	/* --- Constructor --- */

	/**
	 * Constructor.
	 * 
	 * @param model
	 *            the tree model to draw
	 * @param view
	 *            the view using this drawing model
	 */
	public HTDraw(HTModel model, HTView view)
	{
		this.view = view;
		this.model = model;
		HTModelNode root = model.getRoot();
		this.sOrigin = new HTCoordS();
		this.sMax = new HTCoordS();

		this.ray = new double[4];
		this.ray[0] = model.getLength();

		for (int i = 1; i < this.ray.length; i++)
		{
			this.ray[i] = (this.ray[0] + this.ray[i - 1])
					/ (1 + (this.ray[0] * this.ray[i - 1]));
		}

		if (root.isLeaf())
		{
			this.drawRoot = new HTDrawNode(null, root, this);
		} else
		{
			this.drawRoot = new HTDrawNodeComposite(null,
					(HTModelNodeComposite) root, this);
		}
	}

	/* --- Screen coordinates --- */

	/**
	 * Refresh the screen coordinates of the drawing tree.
	 */
	public void refreshScreenCoordinates()
	{
		Insets insets = this.view.getInsets();
		this.sMax.x = (this.view.getWidth() - insets.left - insets.right) / 2;
		this.sMax.y = (this.view.getHeight() - insets.top - insets.bottom) / 2;
		this.sOrigin.x = this.sMax.x + insets.left;
		this.sOrigin.y = this.sMax.y + insets.top;
		this.drawRoot.refreshScreenCoordinates(this.sOrigin, this.sMax);
	}

	/**
	 * Returns the origin of the screen plane. WARNING : this is not a copy but
	 * the original object.
	 * 
	 * @return the origin of the screen plane
	 */
	public HTCoordS getSOrigin()
	{
		return this.sOrigin;
	}

	/**
	 * Return the point representing the up right corner of the screen plane,
	 * thus giving x and y maxima. WARNING : this is not a copy but the original
	 * object.
	 * 
	 * @return the max point
	 */
	public HTCoordS getSMax()
	{
		return this.sMax;
	}

	/* --- Drawing --- */

	/**
	 * Draws the branches of the hyperbolic tree.
	 * 
	 * @param g
	 *            the graphic context
	 */
	public void drawBranches(Graphics2D g)
	{
		this.drawRoot.drawBranches(g);
	}

	/**
	 * Draws the nodes of the hyperbolic tree.
	 * 
	 * @param g
	 *            the graphic context
	 */
	public void drawNodes(Graphics2D g)
	{
		this.drawRoot.drawNodes(g);
	}

	/* --- Translation --- */

	/**
	 * Translates the hyperbolic tree by the given vector.
	 * 
	 * @param t
	 *            the translation vector
	 */
	public void translate(HTCoordE zs, HTCoordE ze)
	{
		isTranslating = true;

		HTCoordE zo = new HTCoordE(this.drawRoot.getOldCoordinates());
		zo.x = -zo.x;
		zo.y = -zo.y;
		HTCoordE zs2 = new HTCoordE(zs);

		zs2.translate(zo);

		HTCoordE t = new HTCoordE();
		double de = ze.d2();
		double ds = zs2.d2();
		double dd = 1.0 - de * ds;
		t.x = (ze.x * (1.0 - ds) - zs2.x * (1.0 - de)) / dd;
		t.y = (ze.y * (1.0 - ds) - zs2.y * (1.0 - de)) / dd;

		if (t.isValid())
		{
			// alpha = 1 + conj(zo)*t
			HTCoordE alpha = new HTCoordE();
			alpha.x = 1 + (zo.x * t.x) + (zo.y * t.y);
			alpha.y = (zo.x * t.y) - (zo.y * t.x);
			// beta = zo + t
			HTCoordE beta = new HTCoordE();
			beta.x = zo.x + t.x;
			beta.y = zo.y + t.y;

			this.drawRoot.specialTrans(alpha, beta);

			this.view.repaint();
		}
	}

	/**
	 * Signal that the translation ended.
	 */
	public void endTranslation()
	{
		isTranslating = false;
		this.drawRoot.endTranslation();
		this.view.repaint();
	}

	/**
	 * Translate the hyperbolic tree so that the given node is put at the origin
	 * of the hyperbolic tree.
	 * 
	 * @param node
	 *            the given HTDrawNode
	 */
	public void translateToOrigin(HTDrawNode node)
	{
		this.view.stopMouseListening();
		isTranslating = true;
		AnimThread t = new AnimThread(node);
		t.start();
	}

	/**
	 * Restores the hyperbolic tree to its origin.
	 */
	public void restore()
	{
		this.drawRoot.restore();
		this.view.repaint();
	}

	/**
	 * Sets the fast mode, where nodes are no more drawed.
	 * 
	 * @param mode
	 *            setting on or off.
	 */
	public void fastMode(boolean mode)
	{
		if (mode != this.fastMode)
		{
			this.fastMode = mode;
			this.drawRoot.fastMode(mode);
			if (mode == false)
			{
				this.view.repaint();
			}
		}
	}

	/**
	 * Sets the long name mode, where full names are drawn.
	 * 
	 * @param mode
	 *            setting on or off.
	 */
	public void longNameMode(boolean mode)
	{
		if (mode != this.longNameMode)
		{
			this.longNameMode = mode;
			this.drawRoot.longNameMode(mode);
			this.view.repaint();
		}
	}

	/**
	 * Sets the klein mode.
	 * 
	 * @param mode
	 *            setting on or off.
	 */
	public void kleinMode(boolean mode)
	{
		if (mode != this.kleinMode)
		{
			HTCoordE zo = new HTCoordE(this.drawRoot.getCoordinates());

			this.kleinMode = mode;
			if (this.kleinMode)
			{
				this.model.setLengthKlein();
			} else
			{
				this.model.setLengthPoincare();
			}
			this.model.layoutHyperbolicTree();
			this.drawRoot.kleinMode(mode);

			this.restore();

			if (!this.kleinMode)
			{
				this.translate(new HTCoordE(), zo.pToK());
			} else
			{
				this.translate(new HTCoordE(), zo.kToP());
			}
			this.endTranslation();
		}
	}

	/* --- Node searching --- */

	/**
	 * Returns the node (if any) whose screen coordinates' zone contains thoses
	 * given in parameters.
	 * 
	 * @param zs
	 *            the given screen coordinate
	 * @return the searched HTDrawNode if found; <CODE>null</CODE> otherwise
	 */
	public HTDrawNode findNode(HTCoordS zs)
	{
		return this.drawRoot.findNode(zs);
	}

	/* --- Inner animation thread --- */

	/**
	 * The AnimThread class implements the thread that do the animation when
	 * clicking on a node.
	 */
	public class AnimThread extends Thread
	{

		private HTDrawNode	node	= null; // node to put at the origin
		private Runnable	tTask	= null; // translation task

		/**
		 * Constructor.
		 * 
		 * @param node
		 *            the node to put at the origin
		 */
		public AnimThread(HTDrawNode node)
		{
			this.node = node;
		}

		/**
		 * Do the animation.
		 */
		@Override
		public void run()
		{
			HTCoordE zn = this.node.getOldCoordinates();
			HTCoordE zf = new HTCoordE();

			int frames = NBR_FRAMES;

			// int nodes = model.getNumberOfNodes();

			double d = zn.d();
			for (int i = 0; i < HTDraw.this.ray.length; i++)
			{
				if (d > HTDraw.this.ray[i])
				{
					frames += NBR_FRAMES / 2;
				}
			}

			double factorX = zn.x / frames;
			double factorY = zn.y / frames;

			for (int i = 1; i < frames; i++)
			{
				zf.x = zn.x - (i * factorX);
				zf.y = zn.y - (i * factorY);
				this.tTask = new TranslateThread(zn, zf);
				try
				{
					SwingUtilities.invokeAndWait(this.tTask);
				} catch (Exception e)
				{
					e.printStackTrace();
				}
			}

			zf.x = 0.0;
			zf.y = 0.0;
			this.tTask = new LastTranslateThread(zn, zf);
			try
			{
				SwingUtilities.invokeAndWait(this.tTask);
			} catch (Exception e)
			{
				e.printStackTrace();
			}
			isTranslating = false;
		}

		/* --- Inner's inner --- */

		public class TranslateThread implements Runnable
		{

			private HTCoordE	zStart	= null;
			private HTCoordE	zEnd	= null;

			public TranslateThread(HTCoordE z1, HTCoordE z2)
			{
				this.zStart = z1;
				this.zEnd = z2;
			}

			public void run()
			{
				HTDraw.this.translate(this.zStart, this.zEnd);
				HTDraw.this.view.repaint();
			}
		}

		public class LastTranslateThread implements Runnable
		{

			private HTCoordE	zStart	= null;
			private HTCoordE	zEnd	= null;

			public LastTranslateThread(HTCoordE z1, HTCoordE z2)
			{
				this.zStart = z1;
				this.zEnd = z2;
			}

			public void run()
			{
				HTDraw.this.translate(this.zStart, this.zEnd);
				HTDraw.this.endTranslation();
				HTDraw.this.view.repaint();
				HTDraw.this.view.startMouseListening();
			}
		}

	}

}
